# -*- coding: utf-8 -*-
# import pandas as pd
from turtle import pd

import paramiko
import urllib3

urllib3.disable_warnings()

from concurrent.futures import ThreadPoolExecutor, as_completed
import threading
from typing import List, Union

import requests
import openpyxl
from openpyxl import load_workbook,utils
from concurrent.futures import ThreadPoolExecutor

from commons.logger import log
from utils.encrypt_tools import DataEncrypt


class Network:
    def get_nasname_by_ip(self, ip):
        """
        根据 IP 获取 nasname
        :param ip:
        :return:
        """
        path = f"https://{ip}:9443/ugreen/v1/wizard/is_initialize?"
        log.info(f"开始尝试从 IP {ip} 获取 nasname，请求路径为 {path}")

        try:
            log.debug(f"正在向 {path} 发送 GET 请求，超时时间设置为 0.3 秒，忽略 SSL 验证")
            response = requests.get(path, timeout=0.3, verify=False)
            log.debug(f"收到来自 {path} 的响应，状态码为 {response.status_code}")

            if response.status_code == 200:
                log.info(f"IP {ip} 的响应状态码为 200，开始解析 JSON 数据")
                nasname = response.json().get('data', {}).get('device_name')
                if nasname:
                    log.info(f"成功从 IP {ip} 获取到 nasname: {nasname}")
                else:
                    log.warning(f"IP {ip} 的响应状态码为 200，但未找到有效的 nasname")
                return nasname
            else:
                log.warning(f"IP {ip} 的响应状态码不为 200，实际状态码为 {response.status_code}，无法获取 nasname")
                return None
        except requests.exceptions.Timeout:
            log.warning(f"请求 IP {ip} 超时，超时时间为 0.3 秒，无法获取 nasname")
            return None
        except requests.exceptions.RequestException as e:
            log.error(f"请求 IP {ip} 时发生网络错误，异常信息: {e}，无法获取 nasname")
            return None
        except ValueError as e:
            log.error(f"解析 IP {ip} 的响应 JSON 数据时发生错误，异常信息: {e}，无法获取 nasname")
            return None
        except Exception as e:
            log.critical(f"发生未知错误，IP: {ip}，异常信息: {e}，无法获取 nasname")
            return None

    def get_ip_by_prefix(self, network_prefix: Union[str, List[str]], nasname: str, thread_num: int = 10) -> Union[
        str, None]:
        """
        最终优化版：解决线程竞争和字符串匹配问题
        :param network_prefix: 网络前缀或列表
        :param nasname: 精确匹配的设备名称
        :param thread_num: 最大并行线程数
        :return: 首个匹配IP或None
        """
        # 初始化线程安全控制
        found_flag = threading.Event()
        result_lock = threading.Lock()
        final_result = None  # 使用普通变量加锁替代列表

        # 清理输入参数
        target_name = nasname.strip().lower()
        prefixes = [network_prefix] if isinstance(network_prefix, str) else network_prefix

        def ip_generator():
            """动态IP生成器，带提前终止"""
            for prefix in prefixes:
                for i in range(1, 256):
                    if found_flag.is_set():
                        return
                    yield f"{prefix}.{i}"

        def check_ip(ip: str) -> Union[str, None]:
            """带数据清洗的IP检查"""
            if found_flag.is_set():
                return None

            try:
                # 获取并清洗设备名称
                raw_name = self.get_nasname_by_ip(ip)
                cleaned_name = raw_name.strip().lower() if raw_name else None

                # 调试日志
                log.debug(f"IP {ip} 返回名称: {repr(raw_name)} (清洗后: {repr(cleaned_name)})")

                if cleaned_name == target_name:
                    with result_lock:
                        if not found_flag.is_set():  # 双重检查锁定
                            found_flag.set()
                            nonlocal final_result
                            final_result = ip
                            log.info(f"线程 {threading.get_ident()} 找到精确匹配: {ip}")
                    return ip
            except Exception as e:
                log.warning(f"IP {ip} 检查异常: {str(e)}")
            return None

        # 执行扫描
        with ThreadPoolExecutor(max_workers=thread_num) as executor:
            futures = {}
            gen = ip_generator()

            try:
                while not found_flag.is_set():
                    # 动态提交批次任务（每次100个）
                    batch = [next(gen) for _ in range(100)]
                    current_futures = {executor.submit(check_ip, ip): ip for ip in batch}
                    futures.update(current_futures)

                    # 实时处理完成的任务
                    for future in as_completed(current_futures):
                        if found_flag.is_set():
                            break
                        if future.result():
                            break
            except StopIteration:
                pass  # IP生成完毕
            finally:
                # 终止所有未开始的任务
                for f in futures:
                    f.cancel()

        # 返回最终结果
        if final_result:
            log.info(f"✅ 成功匹配设备 [{target_name}] → IP: {final_result}")
            return final_result
        else:
            log.warning(f"❌ 未找到设备 [{target_name}] 对应的IP")
            return None

    def verify_check(self,host,username):
        """获取公钥接口"""
        path = f'http://{host}:9999/ugreen/v1/verify/check'
        data = {
            "username": username
        }
        try:
            result = requests.post(path, json=data)
            return result.headers.get('X-Rsa-Token')
        except requests.ConnectionError as e:
            log.error(f"网络连接出现问题，获取公钥请求失败: {e}")
            raise  # 可以选择抛出，或者根据需要返回合适的值并记录日志，这里选择抛出以便上层处理
        except requests.Timeout as e:
            log.error(f"获取公钥请求超时: {e}")
            raise
        except Exception as e:
            log.error(f"获取公钥接口出现其他异常: {e}")
            raise

    def login(self,host,username, password):
        """
        重新封装一个登录方法，根据excel表格中的信息登录
        :return:
        """
        path = f'http://{host}:9999/ugreen/v1/verify/login'
        Rsa_Token = self.verify_check(host,username)
        if Rsa_Token is None:
            log.error("获取公钥失败，无法进行登录操作")
            return None
        try:
            encrypted_passwd = DataEncrypt().rsa_passwd(Rsa_Token, password)
            data_login = {
                "username": username,
                "password": encrypted_passwd,
            }
            response = requests.post(path, json=data_login)
            if response.status_code == 200:
                try:
                    token = response.json().get('data').get('token')
                    log.info(f"登录成功，获取到的token: {token}")
                    return token
                except ValueError as json_error:
                    log.error(f"响应数据无法解析为JSON格式，无法获取token: {json_error}")
                    return None
            else:
                log.error(f"登录请求失败，状态码: {response.status_code}")
                return None
        except requests.ConnectionError as e:
            log.error(f"网络连接出现问题，登录过程出现异常: {e}")
            raise
        except requests.Timeout as e:
            log.error(f"登录请求超时: {e}")
            raise
        except Exception as e:
            log.error(f"登录过程出现其他异常: {e}")
            raise

    def get_nas_name(self,host,token):
        """获取设备名称"""
        path = f'http://{host}:9999/ugreen/v1/sysinfo/machine/common'
        params = {'token': token}
        try:
            response = requests.get(path,params=params)
            if response.status_code == 200:
                try:
                    nasname = response.json().get('data').get('common').get('nas_name')
                    log.info(f"登录成功，获取到的nasname: {nasname}")
                    return nasname
                except ValueError as json_error:
                    log.error(f"响应数据无法解析为JSON格式，无法获取token: {json_error}")
                    return None
            else:
                log.error(f"获取设备名称请求失败，状态码: {response.status_code}")
                return None
        except requests.ConnectionError as e:
            log.error(f"网络连接出现问题，获取设备名称请求失败: {e}")
            raise  # 可以选择抛出，或者根据需要返回合适的值并记录日志，这里选择抛出以便上层处理
        except requests.Timeout as e:
            log.error(f"获取设备名称请求超时: {e}")
            raise
        except Exception as e:
            log.error(f"获取设备名称出现其他异常: {e}")
            raise

    def write_nasname_to_excel(self, file_path):
        """
        向Excel表格中写入设备名称。

        :param file_path: Excel文件路径
        """
        try:
            # 加载工作簿
            log.info(f"开始加载Excel文件: {file_path}")
            workbook = load_workbook(file_path)
            # 获取活动工作表
            sheet = workbook.active

            # 定义列索引
            nas_name_index = 2  # 假设SN设备名称在第3列
            host_index = 5  # 假设设备IP在第6列
            username_index = 6  # 假设账号第7列
            password_index = 7  # 假设密码第8列

            # 遍历行，跳过标题行
            log.info("开始遍历Excel表格中的行")
            for row_num, row in enumerate(sheet.iter_rows(min_row=2, values_only=False), start=2):
                host = row[host_index - 1].value
                username = row[username_index - 1].value
                password = row[password_index - 1].value

                if not host or not username or not password:
                    log.warning(f"第 {row_num} 行的主机、用户名或密码为空，跳过该行")
                    continue

                try:
                    log.info(f"开始处理第 {row_num} 行，主机: {host}，用户名: {username}")
                    token = self.login(host, username, password)
                    if token:
                        nas_name = self.get_nas_name(host, token)
                        row[nas_name_index - 1].value = nas_name
                        log.info(f"第 {row_num} 行的设备名称已更新为: {nas_name}")
                    else:
                        log.error(f"第 {row_num} 行登录失败，无法获取设备名称")
                        row[nas_name_index - 1].value = "登录失败"
                except Exception as e:
                    log.error(f"处理第 {row_num} 行时出现异常: {e}")
                    row[nas_name_index - 1].value = f"异常: {e}"

            # 保存工作簿
            log.info("遍历完成，开始保存Excel文件")
            workbook.save(file_path)
            log.info(f"Excel文件 {file_path} 保存成功")

        except FileNotFoundError:
            log.error(f'路径错误或文件不存在：{file_path}')
        except PermissionError:
            log.error(f'没有权限访问文件：{file_path}')
        except Exception as e:
            log.error(f'处理Excel文件时出现未知异常：{e}')

    def write_proc_to_excel(self, file_path):
        """
        向Excel表格中写入内核版本和更新日期信息。

        :param file_path: Excel文件路径
        """
        try:
            # 加载工作簿
            log.info(f"开始加载Excel文件: {file_path}")
            workbook = load_workbook(file_path)
            # 获取活动工作表
            sheet = workbook.active

            # 定义列索引
            proc_index = 3  # 假设内核版本和日期写入此列
            host_index = 6  # 假设设备IP在第6列
            username_index = 7  # 假设账号第7列
            password_index = 8  # 假设密码第8列

            # 遍历行，跳过标题行
            log.info("开始遍历Excel表格中的行")
            for row_num, row in enumerate(sheet.iter_rows(min_row=2, values_only=False), start=2):
                host = row[host_index-1].value
                username = row[username_index-1].value
                password = row[password_index-1].value

                if not host or not username or not password:
                    log.warning(f"第 {row_num} 行的主机、用户名或密码为空，跳过该行")
                    continue

                try:
                    log.info(f"开始处理第 {row_num} 行，主机: {host}，用户名: {username}")
                    # 创建 SSH 对象
                    ssh = paramiko.SSHClient()
                    # 允许连接不在 know_hosts 文件中的主机
                    ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
                    # 连接服务器
                    ssh.connect(hostname=host, username=username, password=password)
                    # 执行命令
                    stdin, stdout, stderr = ssh.exec_command('cat /proc/version')
                    # 获取命令结果
                    result = stdout.read().decode('utf-8')
                    # 关闭连接
                    ssh.close()

                    # 解析版本信息和更新日期
                    try:
                        # 提取版本信息
                        version_start = result.find('Linux version ') + len('Linux version ')
                        version_end = result.find(' ', version_start)
                        version = result[version_start:version_end]

                        # 提取日期信息
                        date_start = result.find('PREEMPT_DYNAMIC ') + len('PREEMPT_DYNAMIC ')
                        date = result[date_start:].strip()

                        info = f"版本: {version}, 更新日期: {date}"
                        row[proc_index - 1].value = info
                        log.info(f"第 {row_num} 行的内核信息已更新为: {info}")
                    except (IndexError, ValueError):
                        log.error(f"第 {row_num} 行解析内核信息失败，输出为: {result}")
                        row[proc_index - 1].value = "解析失败"

                except paramiko.AuthenticationException:
                    log.error(f"第 {row_num} 行登录失败，认证信息错误，主机: {host}，用户名: {username}")
                    row[proc_index-1].value = "登录失败"
                except paramiko.SSHException as e:
                    log.error(f"第 {row_num} 行 SSH 连接失败，主机: {host}，错误信息: {e}")
                    row[proc_index-1].value = f"SSH 连接失败: {e}"
                except Exception as e:
                    log.error(f"处理第 {row_num} 行时出现未知异常: {e}")
                    row[proc_index-1].value = f"异常: {e}"

            # 保存工作簿
            log.info("遍历完成，开始保存Excel文件")
            workbook.save(file_path)
            log.info(f"Excel文件 {file_path} 保存成功")

        except FileNotFoundError:
            log.error(f'路径错误或文件不存在：{file_path}')
        except PermissionError:
            log.error(f'没有权限访问文件：{file_path}')
        except Exception as e:
            log.error(f'处理Excel文件时出现未知异常：{e}')


    def update_device_ips(self,file_path, network_prefix):
        # 读取 Excel 文件
        excel_file = pd.ExcelFile(file_path)
        df = excel_file.parse(0)

        # 假设设备名称列名为 'Device Name'，设备 IP 列名为 'Device IP'
        if '设备名称' not in df.columns or '设备IP' not in df.columns:
            print("Excel 文件中缺少必要的列（'Device Name' 或 'Device IP'）。")
            return

        # 遍历每一行数据
        for index, row in df.iterrows():
            nasname = row['设备名称']
            try:
                # 调用 get_ip_by_prefix 方法获取设备 IP
                ip = network.get_ip_by_prefix(network_prefix, nasname)
                if ip:
                    # 如果获取到 IP，则更新 Excel 中的对应行
                    df.at[index, '设备IP'] = ip
                    print(f"已更新设备 '{nasname}' 的 IP 为 {ip}。")
                else:
                    df.at[index, '设备IP'] = "未找到"
                    print(f"未获取到设备 '{nasname}' 的 IP，跳过。")
            except Exception as e:
                print(f"获取设备 '{nasname}' 的 IP 时发生错误：{e}，跳过。")

        # 将更新后的数据保存回 Excel 文件
        df.to_excel(file_path, index=False)
        print("Excel 文件更新完成。")


if __name__ == "__main__":

    network = Network()
    # network_prefix = ["172.17.21"]
    network_prefix = ["172.17.110","172.17.20","172.17.21"]
    # network_prefix = ["192.168.74","192.168.75","192.168.78","192.168.79","172.17.110","172.17.20","172.17.21"]
    # network_prefix = ["172.17.20","172.17.21","172.17.40","172.17.50","172.17.60","172.17.80","172.17.90","172.17.110","192.168.1","192.168.73","192.168.74","192.168.78","192.168.79"]

    # nasname = "DH2300PLUS-C08C"
    # nasname = "DXP2800-E403"
    # nasname = "DXP6800-4A82"
    # nasname="DXP4800S"
    # nasname="DXP6800-4A82"
    # nasname = "IDX6011PRO-CE43"
    nasname="DXP4800PLUS-F87"
    ip = network.get_ip_by_prefix(network_prefix, nasname)
    if ip:
        print(f"找到匹配的 IP 地址: {ip}")
    else:
        print("未找到匹配的 IP 地址")
    # # host = "172.17.20.57"
    # # username = "test3"
    # # password = "Aa123456@"
    # # token = network.login(host,username,password)
    # # print(token)
    # # nasname = network.get_nas_name(host,token)
    # # print(nasname)
    # network.write_proc_to_excel('NAS账号.xlsx')
    # network.update_device_ips('NAS账号.xlsx',network_prefix)